-- Maze map generator mod for Minetest
-- Copyright © 2017 Pedro Gimeno Fortea

-- Many thanks to Paramat for the numerous tips with the flat world generator
-- and understanding the VoxelManip, and the people at IRC for their support.

-- Which height to use for the terrain
local terrain_height = 0

-- Flat world generator from here:
-- https://gist.github.com/pgimeno/1e046dd6bacb1624a70b37af5ea1d805

minetest.set_mapgen_setting('mg_name', 'flat', true)
minetest.set_mapgen_setting('mgflat_ground_level', terrain_height, true)
-- water has to be below ground level to not inundate everything;
-- it can't be equal lest you spawn at (0,0,0).
minetest.set_mapgen_setting('water_level', -31000, true)
minetest.set_mapgen_setting('mg_flags', 'light, nocaves, nodungeons, nodecorations', true);

minetest.clear_registered_biomes()
minetest.clear_registered_ores()
minetest.clear_registered_decorations()

minetest.register_biome {
  name = "mazeworld",
  --node_dust = "",
  node_top = "default:dirt_with_dry_grass", -- layer 1
  depth_top = 1,
--  node_filler = "default:dirt", -- layer 2 (not uniform)
--  depth_filler = 0,
  node_stone = "default:desert_stone", -- layer 3 (all the way to the bottom)
  --node_water_top = "",
  --depth_water_top = ,
  --node_water = "",
  --node_river_water = "",
  y_min = -31000,
  y_max = 31000,
  heat_point = 50,
  humidity_point = 50,
}

-- <paramat> you can stack biomes on top of each other if you need changes of
--           materials with depth




-- Maze generator - a variant of the recursive nested algorithm
-- with base size 2 and a method to make long walls less prominent (TODO)

local wall_nodeid = minetest.get_content_id('default:sandstonebrick')
local empty_nodeid = minetest.get_content_id('air')
local maze_height = terrain_height + 1
local worldseed = tonumber(minetest.get_mapgen_setting("seed"))

-- These variables are used to pass values to the functions
-- (a bit ugly, but more comfortable)
local xmin, ymin, zmin, xmax, ymax, zmax
local sx, sy, sxy, base, xbase, zbase

-- This is the VoxelManip data. Make it a top-level local as recommended.
local data = {}


local function intersects(px, pz, size)
  return not (px + size - 1 < xmin or px > xmax
           or pz + size - 1 < zmin or pz > zmax)
end

-- Return a pseudorandom generator that generates a sequence
-- which is always the same for a given size, 2D position,
-- and world seed, but different for each.
local function get_rng(px, pz, size)
  return PcgRandom(PcgRandom(worldseed, size):next() + px, pz)
end

local function setmaze(x, z, id)
  for y = 0, sx, sx do
    data[base + (z - zbase) * sxy + (x - xbase) + y] = id
  end
end

local function setwall(x, z)
  setmaze(x, z, wall_nodeid)
end

local function clearwall(x, z)
  setmaze(x, z, empty_nodeid)
end

local function mazegen_recursive(xpos, zpos, size)
  -- A size-2 maze is essentially a U-shape.
  local rng = get_rng(xpos, zpos, size)
  -- Pick up the direction where the wall is.
  local dir = rng:next(0, 3)
  if size == 2 then
    -- reached the end of the recursion - fill this 4x4 block

    -- first the fixed walls
    setwall(xpos, zpos)
    setwall(xpos + 1, zpos)
    setwall(xpos + 2, zpos)
    setwall(xpos + 3, zpos)
    setwall(xpos, zpos + 1)
    setwall(xpos, zpos + 2)
    setwall(xpos, zpos + 3)
    setwall(xpos + 2, zpos + 2)

    -- now the extra wall
    local ofsx = dir == 0 and 1 or dir == 2 and -1 or 0
    local ofsz = dir == 1 and 1 or dir == 3 and -1 or 0
    setwall(xpos + 2 + ofsx, zpos + 2 + ofsz)
    return
  end

  -- Test all 4 positions at this nesting level, recursing if the
  -- current position touches the block.
  for z = zpos, zpos + size, size do
    for x = xpos, xpos + size, size do
      if intersects(x, z, size) then
        mazegen_recursive(x, z, size / 2)
      end
    end
  end

  if dir ~= 0 then
    -- remove one random block from the wall that extends to the east
    local rnd = rng:next(0, size/2 - 1) * 2 + 1
    if intersects(xpos + size + rnd, zpos + size, 1) then
      clearwall(xpos + size + rnd, zpos + size)
    end
  end
  if dir ~= 1 then
    -- remove one random block from the wall that extends to the north
    local rnd = rng:next(0, size/2 - 1) * 2 + 1
    if intersects(xpos + size, zpos + size + rnd, 1) then
      clearwall(xpos + size, zpos + size + rnd)
    end
  end
  if dir ~= 2 then
    -- remove one random block from the wall that extends to the west
    local rnd = rng:next(0, size/2 - 1) * 2 + 1
    if intersects(xpos + size - rnd, zpos + size, 1) then
      clearwall(xpos + size - rnd, zpos + size)
    end
  end
  if dir ~= 3 then
    -- remove one random block from the wall that extends to the south
    local rnd = rng:next(0, size/2 - 1) * 2 + 1
    if intersects(xpos + size, zpos + size - rnd, 1) then
      clearwall(xpos + size, zpos + size - rnd)
    end
  end

end



minetest.register_on_generated(function (minp, maxp, blockseed)
  local vm, min, max = minetest.get_mapgen_object("voxelmanip")

  -- does block include maze_height or maze_height + 1?
  if minp.y <= maze_height and maxp.y >= maze_height
    or minp.y <= maze_height + 1 and maxp.y >= maze_height + 1
  then

    sx, sy = max.x - min.x + 1, max.y - min.y + 1
    sxy = sx * sy

    base = (maze_height - min.y) * sx + 1
    xbase, zbase = min.x, min.z
    xmin, ymin, zmin = minp.x, minp.y, minp.z
    xmax, ymax, zmax = maxp.x, maxp.y, maxp.z

    -- Prepare to modify the voxelmanip
    vm:get_data(data)

    mazegen_recursive(-32768, -32768, 32768)

    vm:set_data(data)
    vm:write_to_map()
  end
end)
